Architecture of PHAD

To help myself understand how the heck PHAD works & how I can make it less confusing.

Dec 20, 2021 notes

  • Documentation::testSitemapGeneration() executes $phad->buildSitemap(), but phad internals never actually build sitemaps

AFTER REVIEWING THE CODEBASE, thoughts

Javascript FE

I read somewhere that I have onSubmit() handlers in the javascript, too ... but I don't see any javascript in here. Maybe that's a future issue??

Item Loading

it is hidden too deep in resolveAccess() and i find the flow very confusing. It works ... it's just hard to follow & keep my head around

Controller

It's really hard to tell who's responsible for what. The controller necessarily has many functions that are called during the display of a view. The controller contains a lot of the logic which should be handled by helper objects, or at least organized into traits.

Right now, Controller's job is to be a monolith that handles everything you throw at it. In the future, Controller's job will be to manage the state & communicate with other components. Refactoring this is probably first priority

And how do you override the controller? Like if you want to subclass it & provide custom validation functions?

I probably should make an interface for the controller ...

Compilation

Compilation is currently handled by the View class ... which is confusing & doesn't make a lot of sense. There's no clear way to just ... compile a whole folder, or straightforward way to just ... compile a single view.

The compiler itself is quite a monolith. It's almost 500 lines & it has several functions with VERY large bodies. This should probably be refactored so the code is easier to follow, possibly just into some traits. Though in past refactoring, I found it really difficult to change how the compiler worked because of all the nodes its dealing with & relationships and .... idunno. Refactoring the compiler is not a huge priority.

Currently, compilation can basically only be done by requesting the view that needs to be compiled. There's no other, more convenient way to do it.

Submission

There's too many interactions, moving parts ... idunno. It's just messy & needs to be cleaned up. I don't like, for example, that submit() calls 'isSubmissionValid()' creates a FormValidator, which then calls validate() ... like ... it's fine, but it feels so scattered. And there's no way to configure it without overriding the controller.

Other

  • I need some directory structure in the code folder, probably
  • The way it was previously integrated with Liaison is kind of horrible to work with and terrible to understand
  • View class: I don't like it. But ... it works & I don't think it really needs refactored ... I don't think View is really the right name, since most of what it does is provide helper functions for NOT displaying html
  • Filters: I think it's fine, but i should provide more filters by default, including a php: namespace filter that allows you to invoke any built-in function, like php:strtoupper
  • Sitemap Building: Idk ... I ... don't know when/how it is invoked. Like I know I can build a sitemap, but ... how? And do I have to personally manage how often the sitemap is built? Need more clarity. I also don't know how sitemaps are delivered ... curently they compile to the cache dir ... I don't think I ever set up routing for them.

NOTES from reviewing the codebase, below

Just ... notes

  • Views are written in simple html using PHAD features like <route>, item="ItemName" & more
  • \Phad\Compiler takes in this clean html & produces a php + html output that is much more complex that makes all phad features work
  • compiled html+php ... requires a controller named $phad. So compiled output can be eval($compiled_output) as long as $phad is available.
  • A compiled view requires:
    • $phad object (main controller)
    • $phad_mode, to change the output/return value
  • Phad can be used for routing & sitemap generation even if not using any database features
  • filters
  • form features (auto-fill, submit handler ...)
  • attributes for:
    • item nodes .... nodes with item="Blog"
    • property nodes ... nodes with prop="slug" ... filter="ns:whatev"
    • route nodes ...
    • sitemap nodes ...
  • How an item->list is built
  • actions (basically everything phad_mode allows)
    • loading view ... $lia->phad($view_name, $args) adds extra args & manages the compile step?
    • submitting form

Classes

  • Addon (liaison integration, formerly compo):
    • adds global phad() function for loading phad views
    • api to add sitemap handlers & property filters
    • create a Phad\Controller
    • create a Phad\Item() object
  • BlackHole: placeholder object for displaying a view without an object (so all props & methods return empty strings ... prevents errors)
  • Compiler: Compiles clean phad-html into executable views
  • Controller: central object in processing a compiled view for each for the phad_modes, loading item lists, verifying access, building sitemap, and communicating with other bits of software that do some other tasks. Basically, a bloated & unorganized mess
  • Filter: Performs Default filtering ... currently only commonmark md to html
  • FormValidator: Used by Controller to validate a submission. Validates based upon properties on the source node, like type="number", required, minlength, maxlength, and others to come
  • Package (liaison integration): initializes views, routes, and handles routing to a view
  • PDOSubmitter: literally just saves submission to database & handles file upload. does NOT do any verification
  • SitemapBuilder: a very simple utility class for writing sitemap xml to disk
  • View: Loads the actual compiled view file & interfaces with the Compiler (convenience methods for each $phad_mode)

Default/Automatic $args

  • phad_submit_values will be set to $_POST when a form is submitted (I think so anyway ...)

Submission flow

$this refers to the controller. Phad\Controller by default

  1. do view-flow up until the submit step
  2. check $this->onSubmit(...) & $this->isSubmissionValid(...) & proceed or return false
  3. make a pdo submitter & submit() (just save to database if no pdo/mysql errors. uses LilDb)
  4. set lastInsertId on the controller & id on the ItemRow
  5. get the url to redirect to via $this->getSubmitTarget($ItemData, $ItemRow)
  6. redirect & return from the parent compiled-view

View flow

type refers to $phad_mode
$phad refers to the controller object passed to the compiled view.

  1. check if type is get_routes or get_sitemap_data & return early if so
  2. create the Item-info object that contains the access list, other paramaters, and will have it's list of items filled.
  3. check if type is get_item_data & return the item from #2 if yes
  4. $phad->resolveAccess(item): Check if access to the view is granted AND load the item list ... ugh (its doing a lot)
    • loop over $Item->accessList
    • check if $this->hasAccess($Item, $Access_Row_as_object)
      • check if user-role passes. call $this->getUser(), then $this->isUserInRole($user, string $role) (minimal helper functions that deserve better implementations)
      • check if $Item->args['access.name'] is set. if is set & $Access->name is not the same, then return false. else return true
      • load the item list (empty object, submitted values, or values loaded from querying). For load values from query: $this->getPdo(), $this->buildSql(...), $itemList = $pdo->fetchAll()...
    • set properties on $Item accordingly for any passed access (like accessIndex & accessStatus)
  5. $phad->itemListStarted(item): ... internal item stack management
  6. check item->accessStatus
    • if 200, then continue displaying the view
    • else print message declared in <on> nodes.
  7. IF <on> nodes declared inside <access> nodes, then check item->accessIndex & execute+display <on> contents accordingly
  8. start foreach($BlogItem->list as $BlogRow_Index=>$BlogRow):
  9. $Blog = $phad->objectFromRow($BlogItem, $BlogRow);: convert the row (from querying) into an object
  10. if !$phad->hasRowAccess($BlogItem, $Blog), continue to the next loop
  11. check if type is submit. if yes, and submission is successful, then return
    • $phad->submit() ... should invoke a handler???
      • your handler can set $BlogItem->phad_mode = 'cancel_submit'; to stop the submission???
  12. $phad->rowStarted($BlogItem, $Blog);: internal item stack management
  13. execute/print the php+html inside the item="Blog" node.
    • nodes with prop="prop_name" declared will have php auto-inserted into them for getting the prop from the $Blog object
  14. $phad->rowFinished($BlogItem, $Blog): internal item stack management
  15. run the rest of the foreach loops
  16. $phad->itemListFinished($BlogItem);: internal item stack management

Controller

  • user-ship
  • has access
  • loading items
  • ...

Phad Nodes

  • <route pattern="/blog/{slug}/"></route>
  • <sitemap sql="SELECT slug FROM blog" handler="ns:func" ...>: contains other declarations for the sitemap like priority, last_mod, changefreq, etc. the ns:func must be registered with Phad\Addon::addSitemapHandler('ns:func', function(){})
    • must be inside a <route>
  • <access name="whatever" handler="ns:handler" role="user_role" where="Blog.slug LIKE :slug">
  • <on s=404> or <on s=200>: display different messages / run different code based upon the access results. CAN be inside an <access> to limit it's scope, or outside the access to apply to all accesses.
  • <x-item item="Blog"> a node that gets removed while allowing all PHAD features to work.
  • <x-prop prop="title"> a node that gets removed while allowing the prop to be printed
  • <onsubmit> only for forms

Phad node properties

  • <input type="backend" name="slug"> tells phad that slug will be generated on the backend from other submitted values
    • does it forbid slug from being submitted via $_POST???

Handlers

  • sitemap handler ...
  • access handler ...
  • filters

Compiled View

  • The Item an Item is created to hold information. If the item="Blog", then there will be an object $BlogItem
    • $BlogItem->name is Blog, as declared in item="Blog"
    • $BlogItem->accessList = [] & has data added from <access> nodes, such as the query
    • $BlogItem->accessStatus is false & is set 200 if access is granted, 404 if no items are found, (I think it does others, too)
    • $BlogItem->accessIndex is the index in $BlogItem->accessList that was approved for the current user
    • $BlogItem->args is the $args passed in by $lia->view('view_name', [...args])
    • $BlogItem->phad_mode is $phad_mode??display
    • $BlogItem->list WILL contain the array of items, if access is granted. This list is looped over to fill the view's html
    • $BlogItem->properties ONLY set for forms and contains information from the prop-nodes like tagName, type, minlength, and more
  • $phad_mode: pass to view to determine the response
    • display: default, will show the view
    • get_routes: returns an array of routes, as an array of attributes from the <route> nodes
    • get_sitemap_data: return array of sitemap data, as copied from the <sitemap> nodes
    • submit: submits a form by calling $phad->submit($BlogItem, $BlogRow)
    • get_item_data: load the $BlogItem as you would for displaying, but just return it.
    • display_with_empty_object will display the form passing a BlackHole instance so all property values are empty